#include "iuPacketIP.hpp"
#include "iuPacketRaw.hpp"
#include <arpa/inet.h>
#include "iuPacketRaw.hpp"
#include "iuPacketFactory.hpp"

using namespace std;

uint16_t iuPacketIP::m_nextId = 0;

/**
 * @brief Initialise le datagramme IP
 */
iuPacketIP::iuPacketIP()
{
   m_id = 0;
   m_offset = 0;
   m_dontFragment = false;
   m_moreFragment = false;
   m_ttl = 255;
   m_protocol = 0;
   m_data = NULL;
}

/**
 * @brief Detruit le datagramme IP
 */
iuPacketIP::~iuPacketIP()
{
   delete m_data;
}

/**
 * @brief Calcule la longueur du datagramme IP. Lorsque fromBuffer() est appelee, le buffer
 * passe en parametre devra avoir au moins cette taille
 * @return Renvoie la longueur du datagramme IP
 */
uint16_t iuPacketIP::getBufferLen() const
{
   uint16_t len;

   //(Version + IHL) + TOS + Total-Length + Id + (Flags + Offset) + TTL + Prot +
   //CheckSum +  Source + Destination + longueur-donnees
   len = 1 + 1 + 2 + 2 + 2 + 1 + 1 + 2 + 4 + 4;
   if (m_data)
      len += m_data->getBufferLen();

   return len;
}

/**
 * @brief Lit le datagramme IP a partir d'un buffer. Si les donnees correspondent a un
 * protocole connu, les donnees seront egalement lues
 * @param buffer le buffer ou le datagramme IP sera lue
 * @param len la longueur du datagramme IP a lire
 * @return Renvoie le nombre d'octets lus (ici, len)
 * @note Si des donnees sont deja existantes dans la trame actuelle, elles ne seront pas detruites
 */
uint16_t iuPacketIP::fromBuffer(const char *buffer, uint16_t len)
{
   const char *b = buffer;
   uint16_t totalLen;

   //Ignore version, IHL et TOS
   b += 2;

   //Lit la longueur totale du datagramme
   READ16(totalLen, b);

   //Lit l'identifiant
   READ16(m_id, b);

   //Lit les flags
   m_dontFragment = ((*b & 0x40) != 0);
   m_moreFragment = ((*b & 0x20) != 0);

   //Lit le fragment offset
   READ16(m_offset, b);
   m_offset &= 0x1fff;

   //Lit le TTL
   m_ttl = *b;
   b += 1;

   //Lit le protocole
   m_protocol = *b;
   b += 1;

   //Ignore le header checksum
   b += 2;

   //Lit les adresses de source et de destination
   b += m_src.fromBuffer(b);
   b += m_dest.fromBuffer(b);

   //Lit les donnees eventuelles
   if (!isFragment())
      m_data = iuPacketFactory::create(3, m_protocol);
   else
      m_data = new iuPacketRaw();

   if (m_data)
      b += m_data->fromBuffer(b, len - (b - buffer));

   return (b - buffer);
}

/**
 * @brief Ecrit le datagramme IP dans le buffer. Le buffer devra avoir une taille d'au
 * moins getBufferLen() octets
 * @param buffer l'adresse du debut du buffer dans lequel sera ecrit le datagramme IP
 * @return Renvoie le nombre d'octets ecrits (== getBufferLen())
 */
uint16_t iuPacketIP::toBuffer(char *buffer) const
{
   char *b = buffer;
   uint16_t totalLen;
   uint16_t flagsAndOffset;

   //Ecrit la version et l'IHL
   *b = 0x45;
   b += 1;

   //Ecrit le TOS
   *b = 0;
   b += 1;

   //Ecrit la longueur totale du datagramme
   totalLen = 20;
   if (m_data)
      totalLen += m_data->getBufferLen();
   WRITE16(b, totalLen);

   //Ecrit l'identifiant
   WRITE16(b, m_id);

   //Ecrit les flags et le fragment offset
   flagsAndOffset = m_offset | 0x8000;
   if (m_dontFragment)
      flagsAndOffset |= 0x4000;
   if (m_moreFragment)
      flagsAndOffset |= 0x2000;
   WRITE16(b, flagsAndOffset);

   //Ecrit le TTL
   *b = m_ttl;
   b += 1;

   //Ecrit le protocole
   *b = m_protocol;
   b += 1;

   //Ecrit le header checksum a 0
   WRITE16(b, 0);

   //Ecrit l'adresse de source et de destination
   b += m_src.toBuffer(b);
   b += m_dest.toBuffer(b);

   //Ecrit les donnees eventuelles
   if (m_data)
      b += m_data->toBuffer(b);

   return (b - buffer);
}

/**
 * @brief Fragmente le paquet IP suivant la MTU
 * @param mtu La MTU
 * @return Renvoie la liste des packets IP crees!!!
 */
list<iuPacketIP *> iuPacketIP::fragment(uint16_t mtu)
{
   list<iuPacketIP *> fragments;
   char *buffer, *pbuf;
   uint16_t headerSize;
   uint16_t dataSize;
   uint16_t maxSize;
   int i = 0;

   headerSize = 20;
   dataSize = m_data->getBufferLen();
   buffer = (char *)malloc(dataSize);

   //Recuperation du buffer des donnees a fractionner
   m_data->toBuffer(buffer);
   pbuf = buffer;

   //Calcule de la taille maximale du paquet raw
   maxSize = mtu - headerSize;
   maxSize -= maxSize % 8;

   //Creation des differents fragments
   while (dataSize > 0)
   {
      iuPacketIP *newPacket;
      iuPacketRaw *raw;
      bool last;

      last = (dataSize <= maxSize);

      newPacket = new iuPacketIP();
      newPacket->setDestination(m_dest);
      newPacket->setSource(m_src);
      newPacket->setId(m_id);
      newPacket->setTTL(m_ttl);
      newPacket->setFlags(m_dontFragment, !last || m_moreFragment);
      newPacket->setOffset(m_offset + (i * maxSize) / 8);
      newPacket->setProtocol(m_protocol);

      raw = new iuPacketRaw();
      raw->fromBuffer(pbuf, last ? dataSize : maxSize);
      newPacket->setData(raw);

      fragments.push_back(newPacket);
      dataSize -= (last ? dataSize : maxSize);
      pbuf += maxSize;
      i++;
   }

   return fragments;
}

/**
 * @brief Reassemble les fragments donnees dans le paquet IP.
 * @param fragments les fragments a assembler
 */
void iuPacketIP::merge(const list<iuPacketIP *> &fragments)
{
   iuPacketIP *first;
   list<iuPacketIP *>::const_iterator it;
   uint16_t dataLen = 0;
   char *buffer;

   if (fragments.size() == 0)
      return;

   //Recupere les differents champs a partir du premier fragment
   first = *(fragments.begin());
   m_id = first->m_id;
   m_offset = 0;
   m_dontFragment = first->m_dontFragment;
   m_moreFragment = false;
   m_ttl = first->m_ttl;
   m_protocol = first->m_protocol;
   m_src = first->m_src;
   m_dest = first->m_dest;

   //Calcule la longueur totale des donnees encapsulees
   for (it = fragments.begin(); it != fragments.end(); it++)
   {
      iuPacketIP *fragment = *it;

      if (fragment->m_data)
         dataLen += fragment->m_data->getBufferLen();
   }

   //Recopie les donnees de chaque fragment dans un buffer
   buffer = (char *)malloc(dataLen);
   for (it = fragments.begin(); it != fragments.end(); it++)
   {
      iuPacketIP *fragment = *it;

      if (fragment->m_data)
         fragment->m_data->toBuffer(&buffer[fragment->m_offset * 8]);
   }

   //Construit les donnees encapsulees a partir du buffer
   if ((m_data = iuPacketFactory::create(3, m_protocol)))
      m_data->fromBuffer(buffer, dataLen);
   free(buffer);
}

/**
 * @brief Teste si le paquet est un fragment (i.e. pas un datagramme complet)
 * @return Renvoie true si le paquet est un fragment, false sinon
 */
bool iuPacketIP::isFragment()
{
   return (m_moreFragment || m_offset != 0);
}

/**
 * @brief Attribue l'adresse de destination du datagramme IP
 * @param dest l'adresse de destination a attribuer
 */
void iuPacketIP::setDestination(const iuIPAddress &dest)
{
   m_dest = dest;
}

/**
 * @brief Recupere l'adresse de destination du datagramme IP
 * @return Renvoie l'adresse de destination du datagramme IP
 */
const iuIPAddress &iuPacketIP::getDestination() const
{
   return m_dest;
}

/**
 * @brief Attribue l'adresse source du datagramme IP
 * @param src l'adresse source a attribuer
 */
void iuPacketIP::setSource(const iuIPAddress &src)
{
   m_src = src;
}

/**
 * @brief Recupere l'adresse source du datagramme IP
 * @return Renvoie l'adresse source du datagramme IP
 */
const iuIPAddress &iuPacketIP::getSource() const
{
   return m_src;
}

/**
 * @brief Attribue l'identifiant du datagramme IP
 * @param id l'identifiant a attribuer
 */
void iuPacketIP::setId(uint16_t id)
{
   m_id = id;
}

/**
 * @brief Recupere l'identifiant du datagramme IP
 * @return Renvoie l'identifiant du datagramme IP
 */
uint16_t iuPacketIP::getId() const
{
   return m_id;
}

/**
 * @brief Attribue les flags
 * @param dontFragment le frag "dont-fragment" a attribuer
 * @param moreFragment le frag "more-fragment" a attribuer
 */
void iuPacketIP::setFlags(bool dontFragment, bool moreFragment)
{
   m_dontFragment = dontFragment;
   m_moreFragment = moreFragment;
}

/**
 * @brief Recupere les flags
 * @param dontFragment l'adresse ou stockee le flag "dont-fragment"
 * @param moreFragment l'adresse ou stockee le flag "more-fragment"
 */
void iuPacketIP::getFlags(bool *dontFragment, bool *moreFragment) const
{
   if (dontFragment)    *dontFragment = m_dontFragment;
   if (moreFragment)    *moreFragment = m_moreFragment;
}

/**
 * @brief Attribue l'offset du fragment
 * @param offset l'offset a attribuer
 */
void iuPacketIP::setOffset(uint16_t offset)
{
   m_offset = offset;
}

/**
 * @brief Recupere l'offset du fragment
 * @return Renvoie l'offset du fragment
 */
uint16_t iuPacketIP::getOffset() const
{
   return m_offset;
}

/**
 * @brief Attribue le TTL
 * @param ttl le TTL a attribuer
 */
void iuPacketIP::setTTL(uint8_t ttl)
{
   m_ttl = ttl;
}

/**
 * @brief Recupere le TTL
 * @return Renvoie le TTL
 */
uint8_t iuPacketIP::getTTL() const
{
   return m_ttl;
}

/**
 * @brief Attribue le protocole des donnees encapsulees
 * @param protocole le protocole a attribuer
 */
void iuPacketIP::setProtocol(uint8_t protocol)
{
   m_protocol = protocol;
}

/**
 * @brief Recupere le protocole des donnees encapsulees
 * @return Renvoie le protocole des donnees encapsulees
 */
uint8_t iuPacketIP::getProtocol() const
{
   return m_protocol;
}

/**
 * @brief Definit les donnees a encapsuler dans le datagramme IP. Ces donnees seront automatiquement
 * detruites lorsque le datagramme IP sera detruite
 * @param data les donnees a encapsuler
 * @note Si des donnees sont deja existantes dans la trame actuelle, elles ne seront pas detruites
 */
void iuPacketIP::setData(iuPacket *data)
{
   m_data = data;
}

/**
 * @brief Recupere les donnees encapsulees dans le datagramme IP
 * @return Renvoie les donnees encapsulees dans le datagramme IP, ou NULL s'il n'y en a pas
 */
iuPacket *iuPacketIP::getData() const
{
   return m_data;
}

/**
 * @brief Recupere un identifiant unique pour un paquet IP
 * @return Renvoie le nouvel identifiant
 */
uint16_t iuPacketIP::getNextId()
{
   return m_nextId++;
}
